<?
/**
 * This file is part of XNAT light.
 *
 * XNAT light is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * XNAT light is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with XNAT light.  If not, see <http://www.gnu.org/licenses/>.
 */
namespace NRG\Xnat;

use NRG\Framework\Database\SQL\Postgresql as Postgresql;
use NRG\Framework\Exceptions\DatabaseException as DatabaseException;
use NRG\Framework\Exceptions\QueryException as QueryException;
use NRG\Framework\Exceptions\DbConfException as DbConfException;
use NRG\Framework\Exceptions\IllegalArgumentException as IllegalArgumentException;
use NRG\Exceptions\LoginException as LoginException;
use NRG\Exceptions\ACLException as ACLException;
use NRG\Framework\Common\Registry as Registry;
use NRG\Xnat\MrSession as MrSession;
use NRG\Database\Mappers as Mappers;
use NRG\Common\Logger as Logger;
use NRG\Xnat\Security as Security;
use NRG\Framework as Framework;

/**
 * XNAT model
 *
 * @package NRG\Xnat
 */
class Xnat
{
    /**
     * User login
     *
     * @var string
     */
    protected $_username;

    /**
     * User password
     *
     * @var string
     */
    protected $_password;

    /**
     * Database name
     *
     * @var string
     */
    protected $_dbname;

    /**
     * Database username
     *
     * @var string
     */
    protected $_dbusername;

    /**
     * Database password
     *
     * @var string
     */
    protected $_dbpassword;

    /**
     * Database hostname
     *
     * @var string
     */
    protected $_hostname;

    /**
     * Database port
     *
     * @var int
     */
    protected $_port;

    /**
     * Database object
     *
     * @var NRG\Database\Database
     */
    protected $_db;

    /**
     * User object
     *
     * @var NRG\Xnat\User
     */
    protected $_user;

    /**
     * Security object
     *
     * @var NRG\Xnat\Security
     */
    protected $_security;

    /**
     * User authenticated
     *
     * @var bool
     */
    protected $_auth;

    /**
     * Is password encrypted
     *
     * @var boolean
     */
    protected $_encrypted;

    /**
     * Constructor
     *
     * Should be extended by your custom appliaction e.g. NRG\Xnat\Gsp
     *
     * @param string $username XNAT username
     * @param string $password XNAT password
     */
    protected function __construct($username, $password, $encrypted=false)
    {
        // username and password validation
        if(!is_string($username))
            throw new IllegalArgumentException("Password must be a string");
        else if(!is_string($password))
            throw new IllegalArgumentException("Password must be a string");
        else if(!is_bool($encrypted))
            throw new IllegalArgumentException("Encrypted must be boolean");
        else if($username == null)
            throw new IllegalArgumentException("Username cannot be null");
        else if($password == null)
            throw new IllegalArgumentException("Password cannot be null");

        $this->_username = $username;

        if($encrypted)
            $this->_password = $password;
        else
            $this->_password = $this->encrypt($password);

	// store this xnat instance in application registry
	Registry::put("xnat", $this);

	// create security object
        $this->_security = new Security($this);

        // database connection
        $this->dbConnect();

        // login to XNAT
        $this->login();
    }

    /**
     * Retrieve the database object
     *
     * @return NRG\Framework\Database\SQL\PostgreSQL
     */
    public function getDatabase()
    {
        return $this->_db;
    }

    /**
     * Password encryptor
     *
     * @param string $password Text password
     * @return string Encrypted password
     */
    public function encrypt($password=null)
    {
        if(!is_string($password))
            throw new IllegalArgumentException("Password must be a string");
        else if($password == null)
            throw new IllegalArgumentException("Password cannot be null");

        $crypt = "";

        $prime = 373;
        $g = 2;

        for($i = 0; $i < strlen($password); $i++)
            $crypt .= chr($g ^ ord($password[$i]) % $prime);

        return $crypt;
    }

    /**
     * Check for username/password in database
     *
     * @param string $username Username
     * @param string $password Password
     * @return true
     * @throws LoginException
     */
    public function login()
    {
        try
        {
            $this->getUser();
        }
        catch(\Exception $e)
        {
            print_r($e);
            throw new LoginException(LoginException::AUTH_FAILED);
        }

        $auth = true;

        return $auth;
    }

    /**
     * Retrieve user object
     *
     * @return NRG\Xnat\User
     */
    public function getUser()
    {
        if($this->_user === null)
        {
            $mapper = new Mappers\User();

            $this->_user = $mapper->getUser($this->_username, $this->_password);
        }

        return $this->_user;
    }

    /**
     * Access an array of project objects
     *
     * @return array (NRG\Xnat\Project)
     */
    public function getProjects()
    {
        $mapper = new Mappers\Project();
        $projects = $mapper->all();

        foreach($projects as $i => $project)
        {
            if (!$this->_security->canRead($this->_user, $project))
                unset($projects[$i]);
        }

        return $projects;
    }

    /**
     * Retrieve a MR Session object given an Accession ID
     *
     * @param string $accession Accession ID
     * @return NRG\Xnat\MrSession
     * @throws ACLException
     */
    public function getMrSession($accession)
    {
        if(!is_string($accession))
            throw new IllegalArgumentException("Accession ID must be a string");
        else if(trim($accession) == "")
            throw new IllegalArgumentException("Accession ID must be non-empty");

        $mapper = new Mappers\MrSession();

	$mrSession = null;

	try
	{
	    $mrSession = $mapper->byId($accession);
	}
	catch(QueryException $qe)
	{
	    if($qe->getType() == QueryException::EMPTY_RESULT)
		    return null;
	}

        // check access perms
        if(!$this->_security->canRead($this->_user, $mrSession))
            throw new ACLException(ACLException::NO_ACCESS, $accession);

        return $mrSession;
    }

    /**
     * Retrieve a Subject
     *
     * @param string $accession
     * @return NRG\Xnat\Subject
     * @throws ACLException
     */
    public function getSubject($accession)
    {
        if(!is_string($accession))
            throw new IllegalArgumentException("Accession ID must be a string");
        else if(trim($accession) == "")
            throw new IllegalArgumentException("Accession ID must be non-empty");

        $mapper = new Mappers\Subject();

	$subject = null;

	try
	{
	    $subject = $mapper->byId($accession);
	}
	catch(QueryException $qe)
	{
	    if($qe->getType() == QueryException::EMPTY_RESULT)
		    return null;
	}

        // check access perms
        if(!$this->_security->canRead($this->_user, $subject))
            throw new ACLException(ACLException::NO_ACCESS, $accession);
	
        return $subject;
    }

    /**
     * Get Subject IDs for label
     *
     * @param string $label Subject label
     * @return array
     */
    public static function getSubjectID($label,$project=null)
    {
        if(!is_string($label))
            throw new IllegalArgumentException("Label must be a string");
        else if(trim($label) == "")
            throw new IllegalArgumentException("Label must be non-empty");

        $mapper = new Mappers\Subject();
        $ids = $mapper->getAccessionIDs($label,$project);

        return $ids;
    }

    /**
     * Get Subject label for given ID
     *
     * @param string $id Subject ID
     * @return array
     */
    public static function getSubjectLabel($id=null)
    {
        if(!is_string($id))
            throw new IllegalArgumentException("ID must be a string");
        else if($id == "")
            throw new IllegalArgumentException("ID must be non-empty");

        $mapper = new Mappers\Subject();
        $labels = $mapper->getSubjectLabel($id);

        return $labels;
    }

    /**
     * Get MR Session accession IDs given a MR Session label
     *
     * @param string $label MR Session label
     * @return string
     */
    public static function getAccessionIDs($label=null,$project=null)
    {
        if(!is_string($label))
            throw new IllegalArgumentException("Label must be a string");
        else if($label == "")
            throw new IllegalArgumentException("Label must be non-empty");

        $mapper = new Mappers\MrSession();
        $accessionID = $mapper->getAccessionIDs($label,$project);

        return $accessionID;
    }

    /**
     * Check database connection
     *
     * @return boolean
     * @throws DatabaseException
     */
    public function checkDb()
    {
        if($this->_db->checkConnection())
            return true;
        else
            throw new DatabaseException(DatabaseException::NO_CONNECTION);
    }

    /**
     * Run database query
     *
     * @param string $query Parameterized query
     * @param array $params Numeric array of query parameters
     * @return array Associative array of query results
     */
    public function query($query=null, $params=array())
    {
        if(!is_string($query))
            throw new IllegalArgumentException("Query must be a string");
        else if($query == "")
            throw new IllegalArgumentException("Query cannot be null");

        // check db connection
        $this->checkDb();

        // execute the query
        $result = $this->_db->query($query, $params);

        //See Quercus Bug ID #0004378
        $message=trim($this->_db->getLastError());

        if (($result===false) && (!empty($message)))
            throw new QueryException(QueryException::QUERY_FAILED, $query, $params, $message);

        if(!is_bool($result) && (pg_num_rows($result) > 0))
        {
            // fetch result as array
            if(!$result = pg_fetch_all($result))
                throw new QueryException(QueryException::EMPTY_RESULT, $query, $params, $this->_db->getLastError());

            // return array
            return $result;
        }
        else
            return array();
    }

    /**
     * Establish a database connection
     *
     * @return void
     * @throws Exception
     */
    public function dbConnect()
    {
        $db_ini = dirname(__FILE__) . "/db.ini";

        if(!file_exists($db_ini))
            throw new DbConfException(DbConfException::NO_FILE, $db_ini);
        else if(!is_readable($db_ini))
            throw new DbConfException(DbConfException::UNREADABLE, $db_ini);

        $ini = parse_ini_file($db_ini);

        // hostname
        if(!array_key_exists("host", $ini))
            $ini["host"] = "localhost";

        // port
        if(!array_key_exists("port", $ini))
            $ini["port"] = 5432;

        // dbname, username, password
        if(!array_key_exists("username", $ini))
            throw new DbConfException(DbConfException::NO_USERNAME, $db_ini);
        else if(!array_key_exists("password", $ini))
            throw new DbConfException(DbConfException::NO_PASSWORD, $db_ini);
        else if(!array_key_exists("dbname", $ini))
            throw new DbConfException(DbConfException::NO_DBNAME, $db_ini);
        else if(!preg_match("/[0-9]+/", $ini["port"]))
            throw new DbConfException(DbConfException::PORT_FORMAT, $db_ini);

        $this->_db = new Postgresql($ini["dbname"], $ini["username"],
        $ini["password"], $ini["host"], (int) $ini["port"]);
    }
}
